package tcp;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * A session handler is used to handle a connection to another 
 * node, which I call a client here.
 * 
 * Notes: As it turns out, having the server keep a thread open for 
 * each connection is obscenely inefficient.  I'm not convinced that
 * it's totally a bad idea, but instead, that there is something 
 * weird with my Java implementation of it.
 * 
 * So this class is really only useful on the client side now. 
 * SessionHandler will be rewritten to handle the Socket code in 
 * the same thread of execution calling it.
 * 
 * 
 * @author Kristopher L. Kalish, James Madison University
 * @version 1 - February 23, 2008
 * @version 2 - April 6, 2008 (Added send object capabilities/Observer)
 *
 */
public class ThreadedSessionHandler implements Runnable, TCPSubject
{ 
	private volatile boolean  isRunning;    // for threading (volatile?)
	private List<Object>      toSend;
	private List<TCPObserver> observers;
	private Socket            clientSocket;
	private Thread            sessionThread;
	
	private boolean isServer;

	private Object lock = new Object();
	
	/**
	 * The most versatile constructor.
	 * 
	 * @param socket The socket connection to our node/client
	 */
	public ThreadedSessionHandler(Socket socket, boolean isServer)
	{
		this.clientSocket = socket; 
		this.isServer     = isServer;
		
		toSend    = Collections.synchronizedList(new LinkedList<Object>());
		observers = Collections.synchronizedList(new LinkedList<TCPObserver>());
		isRunning = false;
	}
	/**
	 * Explicit constructor. Used to maintain compatibility with the
	 * old version of this class before true observer was implemented.
	 * 
	 * @param socket The socket connection to our node/client
	 * @param tcpo Our sole TCPObserver
	 */
	public ThreadedSessionHandler(Socket socket, TCPObserver tcpo, boolean isServer)
	{
		this(socket, isServer);
		addObserver(tcpo);
	} 
	
	public void addObserver(TCPObserver observer)
	{
		observers.add(observer);
	}
	
	public void notifyObservers(Object o)
	{
		synchronized(lock)
		{
			//System.out.println("Notifying observers..."); //debug
			for(TCPObserver observer : observers)
				observer.handleObject(o);
		}
	}
	
	public void removeObserver(TCPObserver observer)
	{
		observers.remove(observer);
	}
	
	public void run() 
	{
		ObjectInputStream  ois = null;
		//ObjectOutputStream oos = null;
		
		System.out.println("Connected to " + clientSocket.getInetAddress());

		try { 
			//oos = new ObjectOutputStream(clientSocket.getOutputStream());
			ois = new ObjectInputStream(clientSocket.getInputStream());
		} catch(IOException ioe) {
			ioe.printStackTrace();
		}
		System.out.println("About to loop...");//debug
		while(isRunning)
		{
			System.out.println("Inside run while loop."); //debug
			int sent = 0;
			
			synchronized(lock)
			{
				try {
					Object read;
					/*
					//there certainly must be a better way eg. defining a protocol
					if(isServer)
					{
						for(Object obj : toSend)
						{
							//System.out.println("Serializing & sending: " + obj); //debug
							oos.writeObject((Serializable) obj);
							oos.flush();
							oos.reset();
							sent++;
						}
						toSend.clear();
					}
					*/

					//if(sent > 0)
					{
						//TODO: hack here..
						if(ois.available() > 0 || !isServer)
						{
							System.out.println("Attemping to read object..."); //debug
							read = ois.readObject();
							System.out.println("Read something..."); //debug
							notifyObservers(read);	
							
							//oos.writeObject("Got-it!");
							//oos.flush();
							//oos.reset();
						}
					}
						
		
					
					/*
					try {
						Thread.sleep(10);
					} catch(InterruptedException ie) {
					}*/
					
				} catch (EOFException eofe) {
					//System.out.println("sdffs");// do nothing
				} catch (IOException ioe) {
					ioe.printStackTrace(); 
				} catch (ClassNotFoundException cnfe) {
					cnfe.printStackTrace();
				}
			}
		}
		
		try {
			//oos.close();
			ois.close();
			clientSocket.close();
		} catch(IOException ioe) {
			ioe.printStackTrace();
		}
	} 
	
	public void send(Object data)
	{
		synchronized(lock)
		{
			//System.out.println("Adding data to be sent..."); //debug
			toSend.add(data);
		}
	}
	
	/**
	 * Starts the threading.  run() is ultimately called by Java
	 * after calling this method.
	 */
	public void start()
	{
		if(sessionThread == null && !isRunning) 
		{
			isRunning = true;
			//System.out.println("Starting SessionHandler thread."); //debug
			sessionThread = new Thread(this);
			sessionThread.start();
		}
	}
	
	/**
	 * Stops the thread.
	 */
	public void stop()
	{
		isRunning = false;
	}
} 